/**
 * Copyright 2019 吉鼎科技.

 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package cn.easyplatform.contexts;

import cn.easyplatform.EntityNotFoundException;
import cn.easyplatform.FieldNotFoundException;
import cn.easyplatform.ScriptEvalExitException;
import cn.easyplatform.ScriptRuntimeException;
import cn.easyplatform.dao.BizDao;
import cn.easyplatform.dos.FieldDo;
import cn.easyplatform.dos.Record;
import cn.easyplatform.entities.beans.LogicBean;
import cn.easyplatform.entities.beans.list.ListBean;
import cn.easyplatform.entities.beans.table.TableBean;
import cn.easyplatform.entities.beans.task.Variable;
import cn.easyplatform.entities.helper.EventLogic;
import cn.easyplatform.interceptor.CommandContext;
import cn.easyplatform.lang.Lang;
import cn.easyplatform.lang.Strings;
import cn.easyplatform.messages.response.SimpleResponseMessage;
import cn.easyplatform.messages.vos.ListInitVo;
import cn.easyplatform.messages.vos.datalist.ListAuxHeadVo;
import cn.easyplatform.messages.vos.datalist.ListGroupVo;
import cn.easyplatform.messages.vos.datalist.ListHeaderVo;
import cn.easyplatform.messages.vos.datalist.ListQueryParameterVo;
import cn.easyplatform.support.scripting.CompliableScriptEngine;
import cn.easyplatform.support.scripting.ScriptEngineFactory;
import cn.easyplatform.type.EntityType;
import cn.easyplatform.type.IResponseMessage;
import cn.easyplatform.type.ScopeType;
import cn.easyplatform.util.MessageUtils;
import cn.easyplatform.util.RuntimeUtils;

import java.io.Serializable;
import java.util.*;

/**
 * 三级记录信息
 *
 * @author <a href="mailto:davidchen@epclouds.com">littleDog</a> <br/>
 * @since 2.0.0 <br/>
 */
public class ListContext implements Serializable {

    /**
     *
     */
    private static final long serialVersionUID = 7513794305541240910L;

    /**
     * 组件的id
     */
    private String id;

    /**
     * 对应的列表对象
     */
    private ListBean bean;

    /**
     * 初始条件
     */
    private String condition;

    /**
     * 单纯的结果语句，不包括条件和排序
     */
    private String resultSql;

    /**
     * 键值栏位
     */
    private List<String> keyFields;

    /**
     * 查询语句,包括条件语句,不包括排序
     */
    private String sql;

    /**
     * 查询语句的参数
     */
    private List<FieldDo> params;

    /**
     * 排序语句
     */
    private String orderBy;

    /**
     * 分组语句
     */
    private String groupBy;

    /**
     * 查询面板的查询语句
     */
    private String panelQuery;

    /**
     * 查询面板的参数
     */
    private List<FieldDo> panelParams;

    /**
     * 查询面板所对应的栏位值信息,主要应用在功能返回时重现查询信息
     */
    private List<ListQueryParameterVo> panelQueryInfo;

    /**
     * 自定义查询，处理时数据从客户端返回，不通过主KEY来抓取
     */
    private boolean isCustom;

    /**
     * 页笔数
     */
    private int pageSize;

    /**
     * 起始页
     */
    private int startPageNo = -1;

    /**
     * 总笔数
     */
    private int totalSize = -1;

    /**
     * 实始逻辑，可以在这里加载数据
     */
    private String init;

    /**
     * 新增记录时调用，里面包含映射和逻辑
     */
    private String beforeLogic;

    /**
     * 根据列表修改模式： 1.如果是可编辑，在Page.onOk事件之后执行 2.用Open编辑，在commit或save之后执行
     */
    private String afterLogic;

    /**
     * 每行逻辑
     */
    private String onRow;

    /**
     * 是否要获取数据
     */
    private boolean immediate;

    /**
     * 是否锁记录
     */
    private boolean lock;

    /**
     * 列表头
     */
    private List<ListHeaderVo> hvs;

    /**
     * 多重列表头
     */
    private List<ListAuxHeadVo> ahvs;

    /**
     * 分组信息
     */
    private List<ListGroupVo> gvs;

    /**
     * 类型，参考Constants CATELOG和TABLE需要保留在taskcontext中
     * 其它的例如Actionbox、Wandbox、TableLoad在当前taskcontext结束时自动清空
     */
    private String type;

    /**
     * 是否可编辑
     */
    private boolean isEditable;

    /**
     * 来源于另外一个列表id
     */
    private String host;

    /**
     * 新增列表记录时自动给一个主键值
     */
    private int genKeyIndex = 0;

    /**
     * 所属列表的记录主键,对应RecordContext.keyValues
     */
    private String hostKey;

    // 当host不为空时，保留host列表的键值，当用open打开列表编辑窗口时可以同一个窗口新建记录
    private Object[] oldKeyValues;

    /**
     * panel对应的上下文对象
     */
    private RecordContext panelRecord;

    /**
     * 过滤
     */
    private String filter;

    /**
     * 数据源来源于初始化逻辑
     */
    private boolean fromInit;
    /**
     * 子列表，如果删除时目标列表的记录也要一并删除
     */
    protected List<String> children = new ArrayList<String>();

    // 系统变量、自定义变量
    private Map<String, Object> systemVariables = new HashMap<String, Object>();

    private Map<String, FieldDo> userVariables;// = new HashMap<String, FieldDo>();// 公共变量

    protected Map<String, List<RecordContext>> records = new HashMap<String, List<RecordContext>>();

    private WorkflowContext ctx;

    ListContext(CommandContext cc, WorkflowContext ctx, ListInitVo iv,
                ListBean bean, Map<String, Object> systemVariables,
                Map<String, FieldDo> taskVariables) {
        this.ctx = ctx;
        this.id = iv.getId();
        this.type = iv.getType();
        this.isEditable = iv.isEditable();
        this.pageSize = iv.getPageSize();
        this.immediate = iv.isImmediate();
        if (!Strings.isBlank(iv.getInit()))
            this.init = iv.getInit();
        else if (bean.getOnInit() != null)
            this.init = getContent(cc, bean.getOnInit());
        if (!Strings.isBlank(iv.getBefore()))
            this.beforeLogic = iv.getBefore();
        else if (bean.getOnBefore() != null)
            this.beforeLogic = getContent(cc, bean.getOnBefore());
        if (!Strings.isBlank(iv.getAfter()))
            this.afterLogic = iv.getAfter();
        else if (bean.getOnAfter() != null)
            this.afterLogic = getContent(cc, bean.getOnAfter());
        if (bean.getOnRow() != null)
            this.onRow = getContent(cc, bean.getOnRow());
        this.host = iv.getHost();
        this.bean = bean;
        this.orderBy = bean.getOrderBy();
        this.groupBy = bean.getGroupBy();
        this.systemVariables.putAll(systemVariables);
        for (int i = 835; i < 839; i++)
            this.systemVariables.remove("" + i);
        this.lock = iv.isLock();
        if (!Strings.isBlank(iv.getFilter())) {
            EventLogic el = RuntimeUtils.castTo(cc, ctx.getRecord(),
                    iv.getFilter());
            if (el != null)
                this.filter = el.getContent();
        }
        RuntimeUtils.initList(this.systemVariables, iv, bean);
        this.userVariables = taskVariables;
/*        for (FieldDo fd : taskVariables.values()) {
            if (fd.getScope() == ScopeType.PROTECTED
                    || fd.getScope() == ScopeType.PUBLIC)
                if (fd.getShareModel() == Variable.CLONE_VALUE)
                    userVariables.put(fd.getName(), fd.clone());
                else
                    userVariables.put(fd.getName(), fd);
        }*/
        panelRecord = new RecordContext(this.systemVariables, userVariables);
        if (!Strings.isBlank(bean.getTable())) {
            TableBean table = cc.getEntity(bean.getTable());
            panelRecord.setData(RuntimeUtils.createRecord(cc, table, false));
        }
        hostKey = "";
    }

    private String getContent(CommandContext cc, EventLogic el) {
        String content = el.getContent();
        if (Strings.isBlank(content) && !Strings.isBlank(el.getId())) {
            LogicBean lb = cc.getEntity(el.getId());
            if (lb == null)
                throw new EntityNotFoundException(EntityType.LOGIC.getName(),
                        el.getId());
            content = lb.getContent();
        }
        return content;
    }

    public String getOnRow() {
        return onRow;
    }

    public String getCondition() {
        return condition;
    }

    public void setCondition(String condition) {
        this.condition = condition;
    }

    public String getId() {
        return id;
    }

    public boolean isEditable() {
        return isEditable;
    }

    public ListBean getBean() {
        return bean;
    }

    public List<String> getKeyFields() {
        return keyFields;
    }

    public void setKeyFields(List<String> keyFields) {
        this.keyFields = keyFields;
    }

    public String getResultSql() {
        return resultSql;
    }

    public void setResultSql(String resultSql) {
        this.resultSql = resultSql;
    }

    public String getSql() {
        return sql;
    }

    public void setSql(String sql) {
        this.sql = sql;
    }

    public String getOrderBy() {
        return orderBy;
    }

    public String getGroupBy() {
        return groupBy;
    }

    public void setOrderBy(String orderBy) {
        this.orderBy = orderBy;
    }

    public void setCustom(boolean isCustom) {
        this.isCustom = isCustom;
    }

    public boolean isCustom() {
        return isCustom;
    }

    public int getPageSize() {
        return pageSize;
    }

    public void setPageSize(int pageSize) {
        this.pageSize = pageSize;
    }

    public int getTotalSize() {
        return totalSize;
    }

    public void setTotalSize(int totalSize) {
        this.totalSize = totalSize;
    }

    public String getInit() {
        return init;
    }

    public String getBeforeLogic() {
        return beforeLogic;
    }

    public String getAfterLogic() {
        return afterLogic;
    }

    public String getFilter() {
        return filter;
    }

    public boolean isImmediate() {
        return immediate;
    }

    public List<ListHeaderVo> getHeaders() {
        return hvs;
    }

    public void setHeaders(List<ListHeaderVo> hvs) {
        this.hvs = hvs;
    }

    public List<ListAuxHeadVo> getAuxHeads() {
        return ahvs;
    }

    public void setAuxHeads(List<ListAuxHeadVo> ahvs) {
        this.ahvs = ahvs;
    }

    public void setGroups(List<ListGroupVo> groups) {
        this.gvs = groups;
    }

    public List<ListGroupVo> getGroups() {
        return this.gvs;
    }

    public int getStartPageNo() {
        return startPageNo;
    }

    public void setStartPageNo(int startPageNo) {
        this.startPageNo = startPageNo;
    }

    public String getType() {
        return type;
    }

    public boolean isInited() {
        return records.containsKey(hostKey);
    }

    public boolean isFromInit() {
        return fromInit;
    }

    public void setFromInit(boolean fromInit) {
        this.fromInit = fromInit && !Strings.isBlank(init);
    }

    public String lock(CommandContext cc, Object[] keys) {
        if (lock && !isCustom && !Strings.isBlank(bean.getTable())
                && keys != null)
            return cc.getProjectService().getRowLockProvider(cc)
                    .lock(bean.getTable(), keys);
        return null;
    }

    public void clear(int type) {
        if (type == 0) {
            if (records.containsKey(hostKey))
                records.get(hostKey).clear();
            else
                records.put(hostKey, new ArrayList<RecordContext>());
        } else if (type == 1) {
            List<RecordContext> rcl = records.remove(hostKey);
            if (rcl != null)
                rcl.clear();
            rcl = null;
        } else
            records.clear();
    }

    public String getHost() {
        return host;
    }

    public List<FieldDo> getParams() {
        return params;
    }

    public void setParams(List<FieldDo> params) {
        this.params = null;
        this.params = params;
    }

    public String getPanelQuery() {
        return panelQuery;
    }

    public void setPanelQuery(String panelQuery) {
        this.panelQuery = panelQuery;
    }

    public Collection<ListQueryParameterVo> getPanelQueryInfo() {
        return panelQueryInfo;
    }

    public void setPanelQueryInfo(List<ListQueryParameterVo> panelQueryInfo) {
        this.panelQueryInfo = panelQueryInfo;
    }

    public List<FieldDo> getPanelParams() {
        return panelParams;
    }

    public void setPanelParams(List<FieldDo> panelParams) {
        this.panelParams = panelParams;
    }

    public void setHostKey(Object[] hostKey) {
        StringBuilder sb = new StringBuilder();
        for (Object o : hostKey)
            sb.append(o);
        this.hostKey = sb.toString();
        // boolean isInit = records.containsKey(this.hostKey);
        // if (!isInit)
        // records.put(this.hostKey, new ArrayList<RecordContext>());
        // sb = null;
        // return isInit;
    }

    public List<RecordContext> getRecords() {
        if (hostKey == null)
            return null;
        List<RecordContext> result = records.get(hostKey);
        if (result == null)
            return Collections.emptyList();
        return result;
    }

    /**
     * 获取指定层次列表记录
     *
     * @param layer
     * @return
     */
    public List<Map<String, Object>> toMapList(int layer, boolean sel) {
        if (hostKey == null)
            return Collections.emptyList();
        List<RecordContext> result = records.get(hostKey);
        if (result == null)
            return Collections.emptyList();
        List<Map<String, Object>> list = new ArrayList<>(result.size());
        for (RecordContext rc : result) {
            if (sel && !rc.getParameterAsBoolean("853"))
                continue;
            Map<String, Object> rec = rc.getData().toMap();
            for (String id : children) {
                ListContext child = ctx.getList(layer, id);
                Object[] args = new Object[keyFields.size()];
                for (int i = 0; i < args.length; i++)
                    args[i] = rec.get(keyFields.get(i));
                child.setHostKey(args);
                rec.put(child.getId(), child.toMapList(0, sel));
            }
            list.add(rec);
        }
        return list;
    }

    // /////////////////系统变量///////////////////////////
    public void setParameter(String name, Object value) {
        systemVariables.put(name, value);
    }

    public Object getParameter(String name) {
        return systemVariables.get(name);
    }

    public String getParameterAsString(String name) {
        return (String) systemVariables.get(name);
    }

    public int getParameterAsInt(String name) {
        Object obj = systemVariables.get(name);
        if (obj == null)
            return 0;
        if (obj instanceof Number)
            return ((Number) obj).intValue();
        return Integer.parseInt(obj.toString());
    }

    // ///////////////////END/////////////////////////

    // ////////////////列表操作/////////////////////////
    // 新建列表记录
    public RecordContext createRecord(Record record) {
        return new RecordContext(record, systemVariables, userVariables);
    }

    public RecordContext createRecord(Object[] data) {
        int len = hvs.size();
        Record record = new Record();
        RecordContext rc = createRecord(record);
        for (int index = 0; index < len; index++) {
            ListHeaderVo hv = hvs.get(index);
            if (!Strings.isBlank(hv.getField())) {
                FieldDo field = new FieldDo(hv.getType());
                field.setName(hv.getField());
                if (data != null)
                    field.setValue(data[index]);
                record.set(field);
            } else if (hv.getType() != null) {
                FieldDo field = new FieldDo(hv.getType());
                field.setName(hv.getName());
                field.setScope(ScopeType.PRIVATE);
                if (data != null)
                    field.setValue(data[index]);
                rc.setVariable(field);
            }
        }
        rc.setKeyValues(data);
        return rc;
    }

    // 从数据库加载
    public RecordContext createRecord(Object[] keyValues, Record record) {
        return new RecordContext(keyValues, record, systemVariables,
                userVariables);
    }

    //
    public void appendRecord(RecordContext rc) {
        List<RecordContext> rcl = records.get(hostKey);
        if (rcl == null) {
            rcl = new ArrayList<RecordContext>();
            records.put(hostKey, rcl);
        }
        rcl.add(rc);
    }

    public void appendRecord(RecordContext rc, boolean isGenKey) {
        if (isGenKey) {
            genKeyIndex--;
            rc.keyValues = new Object[1];
            rc.keyValues[0] = genKeyIndex + "pp";
        } else if (rc.keyValues == null) {
            rc.keyValues = new Object[rc.record.getKey().size()];
            for (int i = 0; i < rc.record.getKey().size(); i++) {
                rc.keyValues[i] = rc.record.get(rc.record.getKey().get(i))
                        .getValue();
            }
        }
        appendRecord(rc);
    }

    public void removeRecord(RecordContext rc) {
        List<RecordContext> rcl = records.get(hostKey);
        rcl.remove(rc);
    }

    public void removeRecord(Object[] keyValues) {
        List<RecordContext> rcl = records.get(hostKey);
        Iterator<RecordContext> itr = rcl.iterator();
        while (itr.hasNext()) {
            RecordContext rc = itr.next();
            if (Lang.equals(keyValues, rc.getKeyValues())) {
                itr.remove();
                break;
            }
        }
    }

    public RecordContext getRecord(Object[] keyValues) {
        if (keyValues == null)
            return panelRecord;
        this.oldKeyValues = keyValues;
        List<RecordContext> rcl = records.get(hostKey);
        for (RecordContext rc : rcl) {
            if (Lang.equals(keyValues, rc.getKeyValues()))
                return rc;
        }
        return null;
    }

    public RecordContext getRecord() {
        return getRecord(oldKeyValues);
    }

    public RecordContext getRecord(String host) {
        List<RecordContext> rcl = records.get(this.hostKey);
        StringBuilder sb = new StringBuilder();
        for (RecordContext rc : rcl) {
            for (Object o : rc.getKeyValues())
                sb.append(o);
            if (Lang.equals(host, sb.toString()))
                return rc;
            sb.setLength(0);
        }
        return null;
    }

    public List<String> getChildren() {
        return children;
    }

    public String getHostKey() {
        return hostKey;
    }

    public IResponseMessage<?> save(CommandContext cc, Object[] keys, Object[] data) {
        TableBean tb = cc.getEntity(bean.getTable());
        if (tb == null)
            return MessageUtils.entityNotFound(EntityType.TABLE.getName(),
                    bean.getTable());
        RecordContext rc = getRecord(keys);
        if (rc == null)
            return new SimpleResponseMessage();
        int size = hvs.size();
        for (int i = 0; i < size; i++) {
            ListHeaderVo hv = hvs.get(i);
            if (!Strings.isBlank(hv.getField()) && Strings.isBlank(hv.getSql()))
                rc.setValue(hv.getField(), data[i]);
        }
        char code = rc.getParameterAsChar("814");
        if (rc.getParameterAsBoolean("815") && code != 'R') {
            if (!Strings.isBlank(afterLogic)) {
                String c = RuntimeUtils.eval(cc, afterLogic, rc);
                if (!c.equals("0000"))
                    return MessageUtils.byErrorCode(cc, rc, cc
                            .getWorkflowContext().getId(), c);
            }
            if (!isCustom) {
                BizDao dao = cc.getBizDao(tb.getSubType());
                if (code == 'U') {
                    dao.update(cc.getUser(), tb.getId(), rc.getData(), false);
                    rc.setParameter("814", "R");
                    rc.setParameter("815", false);
                } else if (code == 'C') {
                    dao.insert(cc.getUser(), tb, rc.getData(), false);
                    rc.setParameter("814", "R");
                    rc.setParameter("815", false);
                } else if (code == 'D') {
                    dao.delete(cc.getUser(), tb.getId(),
                            RuntimeUtils.createPrimaryKey(tb, rc.getData()),
                            false);
                    removeRecord(rc);
                }
            }
        }
        return new SimpleResponseMessage();
    }

    /**
     * @param cc
     */
    public void next(CommandContext cc) {
        if (!Strings.isBlank(afterLogic)) {
            CompliableScriptEngine compiler = null;
            EventLogic el = RuntimeUtils.castTo(cc, cc.getWorkflowContext()
                    .getRecord(), afterLogic);
            if (el != null)
                compiler = ScriptEngineFactory.createCompilableEngine(cc,
                        el.getContent());
            if (compiler != null) {
                try {
                    for (List<RecordContext> rcs : records.values()) {
                        for (RecordContext rc : rcs) {
                            if (rc.getParameterAsBoolean("815")
                                    && rc.getParameterAsChar("814") != 'D')
                                compiler.eval(rc);
                        }
                    }
                } catch (ScriptEvalExitException ex) {
                    throw new ScriptRuntimeException(ex.getMessage(),
                            cc.getMessage(ex.getMessage(), ex.getRecord()));
                } finally {
                    compiler.destroy();
                }
            }
        }
    }

    public void save(CommandContext cc) {
        if (!isCustom && !records.isEmpty() && !Strings.isBlank(bean.getTable())) {
            TableBean tb = cc.getEntity(bean.getTable());
            BizDao dao = cc.getBizDao(tb.getSubType());
            CompliableScriptEngine compiler = null;
            if (!Strings.isBlank(afterLogic)) {
                EventLogic el = RuntimeUtils.castTo(cc, cc.getWorkflowContext()
                        .getRecord(), afterLogic);
                if (el != null)
                    compiler = ScriptEngineFactory.createCompilableEngine(cc,
                            el.getContent());
            }
            try {
                for (List<RecordContext> rcs : records.values()) {
                    for (RecordContext rc : rcs) {
                        if (rc.getParameterAsBoolean("815")
                                && rc.getParameterAsChar("814") != 'R') {
                            //执行后置逻辑
                            if (compiler != null)
                                compiler.eval(rc);
                            char code = rc.getParameterAsChar("814");
                            rc.record.setKey(tb.getKey());
                            if (code == 'C') {
                                dao.insert(cc.getUser(), tb, rc.record,
                                        false);
                            } else if (code == 'U')
                                dao.update(cc.getUser(), tb.getId(), rc.record,
                                        false);
                            else if (code == 'D') {
                                List<FieldDo> key = new ArrayList<FieldDo>(tb
                                        .getKey().size());
                                for (String name : tb.getKey()) {
                                    FieldDo fd = rc.record.get(name);
                                    if (fd == null)
                                        throw new FieldNotFoundException(
                                                "entity.table.field.not.found",
                                                tb.getId(), name);
                                    key.add(fd);
                                }
                                dao.delete(cc.getUser(), tb.getId(), key, false);
                            }
                        }
                    }
                }
            } catch (ScriptEvalExitException ex) {
                throw new ScriptRuntimeException(ex.getMessage(),
                        cc.getMessage(ex.getMessage(), ex.getRecord()));
            } finally {
                if (compiler != null)
                    compiler.destroy();
            }
        }
    }

    public void reset() {
        for (List<RecordContext> rcs : records.values()) {
            for (Iterator<RecordContext> itr = rcs.iterator(); itr.hasNext(); ) {
                RecordContext rc = itr.next();
                if (rc.getParameterAsBoolean("815")
                        && rc.getParameterAsChar("814") != 'R') {
                    char code = rc.getParameterAsChar("814");
                    if (code == 'C' || code == 'U') {
                        rc.setParameter("814", "R");
                        rc.setParameter("815", false);
                    } else if (code == 'D')
                        itr.remove();
                }
            }
        }
    }

    void applyAll(String name, Object value) {
        for (List<RecordContext> rcs : records.values()) {
            for (Iterator<RecordContext> itr = rcs.iterator(); itr.hasNext(); ) {
                RecordContext rc = itr.next();
                rc.setParameter(name, value);
            }
        }
    }

    // /////////////////END///////////////////////////
}
